Skip to content

Add LangChain workflow span support and refactor LLM invocation#4449

Open
wrisa wants to merge 15 commits intoopen-telemetry:mainfrom
wrisa:langchain-workflow-type
Open

Add LangChain workflow span support and refactor LLM invocation#4449
wrisa wants to merge 15 commits intoopen-telemetry:mainfrom
wrisa:langchain-workflow-type

Conversation

@wrisa
Copy link
Copy Markdown
Contributor

@wrisa wrisa commented Apr 16, 2026

Description

  • Introduces support for tracing LangChain workflow-level operations (first chain) via the callback handler.
  • Adds start/stop workflow invocation tracking alongside the existing LLM inference spans.
  • Refactors the callback handler to adress changes in #feat(util-genai): refactor and make API smaller and more user-friendly #4391.
  • Adds opentelemetry-util-genai as an explicit dependency in pyproject.toml.
  • Includes a workflow example (examples/workflow/main.py)
  • Comprehensive tests covering chain execution tracing.

See sample workflow and inference spans below,
Screenshot 2026-04-20 at 11 39 40 AM

Fixes # (issue)

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

  • Test A

Does This PR Require a Core Repo Change?

  • Yes. - Link to PR:
  • No.

Checklist:

See contributing.md for styleguide, changelog guidelines, and more.

  • Followed the style guidelines of this project
  • Changelogs have been updated
  • Unit tests have been added
  • Documentation has been updated

@wrisa wrisa changed the title Add workflow and refactor LLM for langchain Add LangChain workflow span support and refactor LLM invocation Apr 16, 2026
@wrisa wrisa marked this pull request as ready for review April 23, 2026 16:45
@wrisa wrisa requested a review from a team as a code owner April 23, 2026 16:45
@lmolkova lmolkova requested a review from Copilot April 23, 2026 17:44
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds workflow-level tracing for LangChain (top-level chain runs) and refactors LLM span creation to use the newer GenAI invocation APIs, including an example and expanded test coverage.

Changes:

  • Add workflow invocation (INTERNAL span) start/stop/error handling via on_chain_* callbacks.
  • Refactor LLM spans to use InferenceInvocation (start_inference(), stop(), fail()), and introduce workflow invocation tracking.
  • Add opentelemetry-util-genai dependency plus a LangGraph workflow example and new tests.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
instrumentation-genai/opentelemetry-instrumentation-langchain/tests/test_workflow_chain.py Adds unit tests validating workflow span creation, CSA propagation, and error/no-op paths.
instrumentation-genai/opentelemetry-instrumentation-langchain/src/opentelemetry/instrumentation/langchain/invocation_manager.py Makes invocation state nullable to support runs without an associated GenAI invocation.
instrumentation-genai/opentelemetry-instrumentation-langchain/src/opentelemetry/instrumentation/langchain/callback_handler.py Implements workflow spans for chains and migrates LLM handling to InferenceInvocation.
instrumentation-genai/opentelemetry-instrumentation-langchain/pyproject.toml Updates core instrumentation dependency and adds explicit opentelemetry-util-genai dependency.
instrumentation-genai/opentelemetry-instrumentation-langchain/examples/workflow/requirements.txt Adds dependencies for the new workflow example.
instrumentation-genai/opentelemetry-instrumentation-langchain/examples/workflow/main.py Adds a LangGraph StateGraph workflow example that invokes an LLM node.
instrumentation-genai/opentelemetry-instrumentation-langchain/CHANGELOG.md Notes the new workflow span support/refactor in the changelog.

Comment on lines +79 to +86
else:
# TODO: For agent invocation
self._invocation_manager.add_invocation_state(
run_id,
parent_run_id,
None, # type: ignore[arg-type]
)

Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nested chains are recorded in the invocation manager with invocation=None, but on_chain_end() returns early when the invocation is missing/non-WorkflowInvocation. If parent_run_id is not present in the manager (e.g., out-of-order callbacks or partial instrumentation), this creates orphaned entries that will never be cleaned up. Consider deleting the invocation state on on_chain_end() / on_chain_error() when get_invocation() returns None (or when it’s not a WorkflowInvocation), or avoid storing state at all when the parent is unknown.

Suggested change
else:
# TODO: For agent invocation
self._invocation_manager.add_invocation_state(
run_id,
parent_run_id,
None, # type: ignore[arg-type]
)
parent_invocation = self._invocation_manager.get_invocation(
run_id=parent_run_id
)
if parent_invocation is None or not isinstance(
parent_invocation, WorkflowInvocation
):
# Do not record nested chain state when the parent is unknown or
# not a workflow; otherwise we can create orphaned entries that
# on_chain_end/on_chain_error cannot clean up.
return
# TODO: For agent invocation
self._invocation_manager.add_invocation_state(
run_id,
parent_run_id,
None, # type: ignore[arg-type]
)

Copilot uses AI. Check for mistakes.
Comment on lines +95 to +100
invocation = self._invocation_manager.get_invocation(run_id=run_id)
if invocation is None or not isinstance(
invocation, WorkflowInvocation
):
# If the invocation does not exist, we cannot set attributes or end it
return
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nested chains are recorded in the invocation manager with invocation=None, but on_chain_end() returns early when the invocation is missing/non-WorkflowInvocation. If parent_run_id is not present in the manager (e.g., out-of-order callbacks or partial instrumentation), this creates orphaned entries that will never be cleaned up. Consider deleting the invocation state on on_chain_end() / on_chain_error() when get_invocation() returns None (or when it’s not a WorkflowInvocation), or avoid storing state at all when the parent is unknown.

Copilot uses AI. Check for mistakes.
@dataclass
class _InvocationState:
invocation: GenAIInvocation
invocation: Optional[GenAIInvocation]
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that _InvocationState.invocation is Optional[GenAIInvocation], _InvocationManager.add_invocation_state(...) should accept Optional[GenAIInvocation] as well (and callers should no longer need # type: ignore[arg-type]). Updating the manager’s method signature and any related typing will keep the public surface consistent and prevent type-suppression from hiding real issues.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use copilot's suggestion earlier to just return when you have no invocation, we don't need to set optional here. Either path would work

top_p=0.9,
frequency_penalty=0.5,
presence_penalty=0.5,
stop_sequences=["\n", "Human:", "AI:"],
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChatOpenAI typically expects stop (not stop_sequences) for stop tokens; using an unsupported constructor kwarg will raise at runtime and make the example fail. Update the example to use the correct parameter name(s) supported by langchain_openai.ChatOpenAI for the pinned langchain==0.3.21.

Suggested change
stop_sequences=["\n", "Human:", "AI:"],
stop=["\n", "Human:", "AI:"],

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +8
# Uncomment after langchain instrumentation is released
# opentelemetry-instrumentation-langchain~=2.0b0.dev No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can delete this if not needed anymore

@dataclass
class _InvocationState:
invocation: GenAIInvocation
invocation: Optional[GenAIInvocation]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use copilot's suggestion earlier to just return when you have no invocation, we don't need to set optional here. Either path would work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants